Coverage Report

Created: 2025-05-07 21:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\tools.proto\tools.proto\compiler\src\api\core\loader.rs
Line
Count
Source
1
// Copyright (c) 2024, BlockProject 3D
2
//
3
// All rights reserved.
4
//
5
// Redistribution and use in source and binary forms, with or without modification,
6
// are permitted provided that the following conditions are met:
7
//
8
//     * Redistributions of source code must retain the above copyright notice,
9
//       this list of conditions and the following disclaimer.
10
//     * Redistributions in binary form must reproduce the above copyright notice,
11
//       this list of conditions and the following disclaimer in the documentation
12
//       and/or other materials provided with the distribution.
13
//     * Neither the name of BlockProject 3D nor the names of its contributors
14
//       may be used to endorse or promote products derived from this software
15
//       without specific prior written permission.
16
//
17
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
use crate::api::core::Error;
30
use crate::compiler::util::imports::ImportSolver;
31
use crate::compiler::util::protocols::{Entry, ProtocolStore};
32
use crate::model::protocol::Import;
33
use crate::{compiler, model};
34
use bp3d_debug::{error, trace};
35
use std::borrow::Cow;
36
use std::path::Path;
37
38
#[derive(Debug, Clone, Default)]
39
pub struct Options<'a> {
40
    package: &'a str,
41
    exclude_from_generation: bool,
42
}
43
44
impl<'a> Options<'a> {
45
8
    pub fn from_package(package: &'a str) -> Self {
46
8
        Self {
47
8
            package,
48
8
            exclude_from_generation: false,
49
8
        }
50
8
    }
51
52
1
    pub fn exclude_from_generation(&mut self) -> &mut Self {
53
1
        self.exclude_from_generation = true;
54
1
        self
55
1
    }
56
57
110
    pub fn is_excluded_from_generation(&self) -> bool {
58
110
        self.exclude_from_generation
59
110
    }
60
}
61
62
pub struct Loader<'a> {
63
    models: Vec<Entry<model::Protocol, Options<'a>>>,
64
    max_iterations: usize,
65
}
66
67
impl Default for Loader<'_> {
68
3
    fn default() -> Self {
69
3
        Self::new(16)
70
3
    }
71
}
72
73
impl<'a> Loader<'a> {
74
27
    pub fn new(max_iterations: usize) -> Self {
75
27
        Self {
76
27
            models: Vec::new(),
77
27
            max_iterations,
78
27
        }
79
27
    }
80
81
7
    pub fn load_from_folder(&mut self, path: impl AsRef<Path>, options: &Options<'a>) -> Result<(), Error> {
82
7
        trace!({path=?path.as_ref()} {?options}, "Loading folder");
83
134
        for a in 
std::fs::read_dir7
(
path7
).
map_err7
(Error::Io)
?0
{
84
134
            let file = a.map_err(Error::Io)
?0
;
85
134
            if file.file_name().as_encoded_bytes().ends_with(b".json5") {
  Branch (85:16): [True: 20, False: 4]
  Branch (85:16): [Folded - Ignored]
  Branch (85:16): [True: 19, False: 3]
  Branch (85:16): [Folded - Ignored]
  Branch (85:16): [True: 38, False: 6]
  Branch (85:16): [True: 19, False: 3]
  Branch (85:16): [True: 19, False: 3]
86
115
                self.load_from_file(file.path(), options)
?0
87
19
            }
88
        }
89
7
        Ok(())
90
7
    }
91
92
116
    pub fn load_from_file(&mut self, path: impl AsRef<Path>, options: &Options<'a>) -> Result<(), Error> {
93
116
        trace!({path=?path.as_ref()} {?options}, "Loading file");
94
116
        let content = std::fs::read_to_string(path).map_err(Error::Io)
?0
;
95
116
        self.load_from_string(content, options)
96
116
    }
97
98
136
    pub fn load_from_string(&mut self, content: impl AsRef<str>, options: &Options<'a>) -> Result<(), Error> {
99
136
        trace!({content=content.as_ref()} {?options}, "Loading string");
100
136
        let model: model::Protocol = json5::from_str(content.as_ref()).map_err(Error::Model)
?0
;
101
136
        if model.imports.as_ref().map(|v| 
v31
.
len31
()).unwrap_or_default() > 0 {
  Branch (101:12): [True: 6, False: 15]
  Branch (101:12): [Folded - Ignored]
  Branch (101:12): [True: 5, False: 14]
  Branch (101:12): [Folded - Ignored]
  Branch (101:12): [True: 0, False: 5]
  Branch (101:12): [True: 10, False: 28]
  Branch (101:12): [True: 5, False: 14]
  Branch (101:12): [True: 0, False: 1]
  Branch (101:12): [True: 0, False: 1]
  Branch (101:12): [True: 0, False: 4]
  Branch (101:12): [True: 0, False: 3]
  Branch (101:12): [True: 0, False: 5]
  Branch (101:12): [True: 5, False: 14]
  Branch (101:12): [True: 0, False: 1]
102
31
            self.models.insert(
103
31
                0,
104
31
                Entry {
105
31
                    userdata: options.clone(),
106
31
                    model,
107
31
                },
108
31
            );
109
105
        } else {
110
105
            self.models.push(Entry {
111
105
                userdata: options.clone(),
112
105
                model,
113
105
            });
114
105
        }
115
136
        Ok(())
116
136
    }
117
118
6
    pub fn exclude(&mut self, name: &str) {
119
114
        
self.models6
.
retain6
(|entry| entry.model.name != name);
120
6
    }
121
122
27
    pub fn compile<T: ImportSolver>(mut self, solver: &T) -> Result<ProtocolStore<T, Options<'a>>, Error> {
123
27
        let mut protocols = ProtocolStore::new(solver);
124
27
        let mut iterations = self.max_iterations;
125
155
        while !self.models.is_empty() && 
iterations > 0148
{
  Branch (125:15): [True: 23, False: 2]
  Branch (125:42): [True: 23, False: 0]
  Branch (125:15): [Folded - Ignored]
  Branch (125:42): [Folded - Ignored]
  Branch (125:15): [True: 21, False: 1]
  Branch (125:42): [True: 21, False: 0]
  Branch (125:15): [Folded - Ignored]
  Branch (125:42): [Folded - Ignored]
  Branch (125:15): [True: 5, False: 0]
  Branch (125:42): [True: 5, False: 0]
  Branch (125:15): [True: 84, False: 4]
  Branch (125:42): [True: 84, False: 0]
  Branch (125:15): [True: 1, False: 0]
  Branch (125:42): [True: 1, False: 0]
  Branch (125:15): [True: 1, False: 0]
  Branch (125:42): [True: 1, False: 0]
  Branch (125:15): [True: 4, False: 0]
  Branch (125:42): [True: 4, False: 0]
  Branch (125:15): [True: 3, False: 0]
  Branch (125:42): [True: 3, False: 0]
  Branch (125:15): [True: 5, False: 0]
  Branch (125:42): [True: 5, False: 0]
  Branch (125:15): [True: 1, False: 0]
  Branch (125:42): [True: 1, False: 0]
126
148
            let entry = self.models.pop().unwrap();
127
148
            trace!({imports=?entry.model.imports} {iterations=?iterations}, "Solving imports for model {}", entry.model.name);
128
148
            let check_not_exists = |package: &str, import: &Import| 
{133
129
133
                let full_name = if package.is_empty() || 
import.protocol.contains("::")89
{
  Branch (129:36): [True: 22, False: 1]
  Branch (129:58): [True: 1, False: 0]
  Branch (129:36): [Folded - Ignored]
  Branch (129:58): [Folded - Ignored]
  Branch (129:36): [True: 22, False: 0]
  Branch (129:58): [True: 0, False: 0]
  Branch (129:36): [Folded - Ignored]
  Branch (129:58): [Folded - Ignored]
  Branch (129:36): [True: 0, False: 0]
  Branch (129:58): [True: 0, False: 0]
  Branch (129:36): [True: 0, False: 88]
  Branch (129:58): [True: 0, False: 88]
  Branch (129:36): [True: 0, False: 0]
  Branch (129:58): [True: 0, False: 0]
  Branch (129:36): [True: 0, False: 0]
  Branch (129:58): [True: 0, False: 0]
  Branch (129:36): [True: 0, False: 0]
  Branch (129:58): [True: 0, False: 0]
  Branch (129:36): [True: 0, False: 0]
  Branch (129:58): [True: 0, False: 0]
  Branch (129:36): [True: 0, False: 0]
  Branch (129:58): [True: 0, False: 0]
  Branch (129:36): [True: 0, False: 0]
  Branch (129:58): [True: 0, False: 0]
130
45
                    Cow::Borrowed(&import.protocol)
131
                } else {
132
88
                    Cow::Owned(format!("{}::{}", package, import.protocol))
133
                };
134
133
                trace!("Searching for: {}", full_name);
135
133
                protocols.get(&full_name).is_none()
136
133
            };
137
148
            if entry
  Branch (137:16): [True: 3, False: 20]
  Branch (137:16): [Folded - Ignored]
  Branch (137:16): [True: 3, False: 18]
  Branch (137:16): [Folded - Ignored]
  Branch (137:16): [True: 0, False: 5]
  Branch (137:16): [True: 12, False: 72]
  Branch (137:16): [True: 0, False: 1]
  Branch (137:16): [True: 0, False: 1]
  Branch (137:16): [True: 0, False: 4]
  Branch (137:16): [True: 0, False: 3]
  Branch (137:16): [True: 0, False: 5]
  Branch (137:16): [True: 0, False: 1]
138
148
                .model
139
148
                .imports
140
148
                .as_ref()
141
148
                .map(|v| 
v.iter()49
.
any49
(|v|
check_not_exists133
(
entry.userdata.package133
,
v133
)))
142
148
                .unwrap_or_default()
143
            {
144
18
                self.models.insert(0, entry);
145
18
                iterations -= 1;
146
18
                continue;
147
130
            }
148
130
            let 
proto110
= compiler::Protocol::from_model(entry.model, &protocols, entry.userdata.package)
149
130
                .map_err(Error::Compiler)
?20
;
150
110
            protocols.insert(Entry {
151
110
                model: proto,
152
110
                userdata: entry.userdata,
153
110
            });
154
        }
155
7
        if iterations == 0 && 
!self.models.is_empty()0
{
  Branch (155:12): [True: 0, False: 2]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [Folded - Ignored]
  Branch (155:31): [Folded - Ignored]
  Branch (155:12): [True: 0, False: 1]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [Folded - Ignored]
  Branch (155:31): [Folded - Ignored]
  Branch (155:12): [True: 0, False: 0]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [True: 0, False: 4]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [True: 0, False: 0]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [True: 0, False: 0]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [True: 0, False: 0]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [True: 0, False: 0]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [True: 0, False: 0]
  Branch (155:31): [True: 0, False: 0]
  Branch (155:12): [True: 0, False: 0]
  Branch (155:31): [True: 0, False: 0]
156
0
            error!(
157
0
                "Failed to solve protocol import order in {} iterations, {} model(s) could not be solved...",
158
                self.max_iterations,
159
0
                self.models.len()
160
            );
161
0
            return Err(Error::SolverMaxIterations);
162
7
        }
163
7
        Ok(protocols)
164
27
    }
165
}